#include <QCoreApplication>
#include <QTimer>
#include <QSerialPort>
#include <QThread>
#include <QDateTime>
#include <QTextCodec>
#include <QDebug>

#include "setting/JSON_settings.h"
#include "coordinator/coordinator.h"
#include "ui_ctrl/ui_controller.h"

UIController_Worker::UIController_Worker(QObject *parent)
	: QObject(parent)
{
	m_port = nullptr;
	m_read_timer = nullptr;

	m_gb_codec = QTextCodec::codecForName("GB18030");
}

UIController_Worker::~UIController_Worker()
{
	stop_work();
}

void UIController_Worker::start_work()
{
    qInfo() << "[UI]Connecting Display Module at " << m_param.port;
    qInfo() << "[UI]    Baud: " << m_param.baud;
    qInfo() << "[UI]DataBits: " << m_param.data_bits;
    qInfo() << "[UI]  Parity: " << m_param.parity;
    qInfo() << "[UI]StopBits: " << m_param.stop_bits;

	m_port = new QSerialPort(m_param.port, this);
	if (m_port) {
		m_port->setBaudRate(m_param.baud);
        m_port->setDataBits(m_param.data_bits);
		m_port->setStopBits(m_param.stop_bits);
		m_port->setParity(m_param.parity);
		if (!m_port->open(QIODevice::ReadWrite)) {
            qInfo() << "[UI]Open Serial Port " << m_param.port << " Failed!";
            delete m_port;
            m_port = nullptr;
            QTimer::singleShot(10000, this, &UIController_Worker::start_work);
			return;
		}
	}

    m_data.clear();
	m_read_timer = new QTimer(this);

    connect(m_read_timer, &QTimer::timeout, this, &UIController_Worker::receive_data);
    m_read_timer->start(200);
}

void UIController_Worker::stop_work()
{
	if (m_read_timer)
		delete m_read_timer;
	if (m_port)
		delete m_port;
	m_read_timer = nullptr;
	m_port = nullptr;
}

void UIController_Worker::setSerialParam(SerialConfig param)
{
	UIController_Worker::m_param = param;
}

void UIController_Worker::set_screen(uint32_t scrn_id)
{
	const uint8_t cmd[] = { 0xee, 0xb1, 0x00, 0x00, 0x00, 0xff, 0xfc, 0xff, 0xff };
	QByteArray ba_cmd((char *) cmd, sizeof(cmd));
	ba_cmd[3] = (scrn_id >> 8) & 0xff;
	ba_cmd[4] = (scrn_id & 0xff);

	qInfo() << "[UI]Switch to screen " << scrn_id;

	transfer_data(ba_cmd);
}

void UIController_Worker::set_btn_status(uint32_t widget_id, uint32_t pressed)
{
    const uint8_t cmd[] =  { 0xee, 0xb1, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xfc, 0xff, 0xff };
	QByteArray ba_cmd((char *) cmd, sizeof(cmd));
	uint32_t scrn_id, btn_id;
	scrn_id = widget_id >> 16;
	btn_id = widget_id & 0x0000ffff;
	ba_cmd[3] = (scrn_id >> 8) & 0xff;
	ba_cmd[4] = (scrn_id & 0xff);
	ba_cmd[5] = (btn_id >> 8) & 0xff;
	ba_cmd[6] = (btn_id & 0xff);
	ba_cmd[7] = (pressed) ? 0x01 : 0x00;

	transfer_data(ba_cmd);
}

void UIController_Worker::set_text(uint32_t widget_id, QString s)
{
	const uint8_t cmd[] = { 0xee, 0xb1, 0x10, 0x00, 0x00, 0x00, 0x00, 0xff, 0xfc, 0xff, 0xff };
	QByteArray ba_cmd((char *) cmd, sizeof(cmd));
	uint32_t scrn_id, text_id;
	scrn_id = widget_id >> 16;
	text_id = widget_id & 0x0000ffff;
	ba_cmd[3] = (scrn_id >> 8) & 0xff;
	ba_cmd[4] = (scrn_id & 0xff);
	ba_cmd[5] = (text_id >> 8) & 0xff;
	ba_cmd[6] = (text_id & 0xff);

	QByteArray ba_str = m_gb_codec->fromUnicode(s);
	ba_str.append((char) 0x00);
    ba_cmd.insert(7, ba_str);

	transfer_data(ba_cmd);
}

void UIController_Worker::set_icon(uint32_t widget_id, uint32_t icon_idx)
{
	const uint8_t cmd[] = { 0xee, 0xb1, 0x23, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xfc, 0xff, 0xff };
	QByteArray ba_cmd((char *) cmd, sizeof(cmd));
	uint32_t scrn_id, text_id;
	scrn_id = widget_id >> 16;
	text_id = widget_id & 0x0000ffff;
	ba_cmd[3] = (scrn_id >> 8) & 0xff;
	ba_cmd[4] = (scrn_id & 0xff);
	ba_cmd[5] = (text_id >> 8) & 0xff;
	ba_cmd[6] = (text_id & 0xff);
	ba_cmd[7] = icon_idx;

	transfer_data(ba_cmd);
}

void UIController_Worker::set_select(uint32_t widget_id, uint32_t val)
{
	const uint8_t cmd[] = { 0xee, 0xb1, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xfc, 0xff, 0xff };
	QByteArray ba_cmd((char *) cmd, sizeof(cmd));
	uint32_t scrn_id, text_id;
	scrn_id = widget_id >> 16;
	text_id = widget_id & 0x0000ffff;
	ba_cmd[3] = (scrn_id >> 8) & 0xff;
	ba_cmd[4] = (scrn_id & 0xff);
	ba_cmd[5] = (text_id >> 8) & 0xff;
	ba_cmd[6] = (text_id & 0xff);
	ba_cmd[7] = val;

	transfer_data(ba_cmd);
}

void UIController_Worker::set_enable(uint32_t widget_id, bool enable)
{
	const uint8_t cmd[] = { 0xee, 0xb1, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xfc, 0xff, 0xff };
	QByteArray ba_cmd((char *) cmd, sizeof(cmd));
	uint32_t scrn_id, text_id;
	scrn_id = widget_id >> 16;
	text_id = widget_id & 0x0000ffff;
	ba_cmd[3] = (scrn_id >> 8) & 0xff;
	ba_cmd[4] = (scrn_id & 0xff);
	ba_cmd[5] = (text_id >> 8) & 0xff;
	ba_cmd[6] = (text_id & 0xff);
	ba_cmd[7] = enable;

	transfer_data(ba_cmd);
}

void UIController_Worker::set_table_data(uint32_t widget_id, QStringList s_list)
{
	const uint8_t clrcmd[] = { 0xee, 0xb1, 0x53, 0x00, 0x00, 0x00, 0x00, 0xff, 0xfc, 0xff, 0xff };
	QByteArray ba_cmd((char *) clrcmd, sizeof(clrcmd));
	uint32_t scrn_id, text_id;
	scrn_id = widget_id >> 16;
	text_id = widget_id & 0x0000ffff;
	ba_cmd[3] = (scrn_id >> 8) & 0xff;
	ba_cmd[4] = (scrn_id & 0xff);
	ba_cmd[5] = (text_id >> 8) & 0xff;
	ba_cmd[6] = (text_id & 0xff);
	transfer_data(ba_cmd);

	const uint8_t setcmd[] = { 0xee, 0xb1, 0x52, 0x00, 0x00, 0x00, 0x00, 0xff, 0xfc, 0xff, 0xff };
    foreach(auto s, s_list) {
		ba_cmd = QByteArray((char *) setcmd, sizeof(setcmd));
		scrn_id = widget_id >> 16;
		text_id = widget_id & 0x0000ffff;
		ba_cmd[3] = (scrn_id >> 8) & 0xff;
		ba_cmd[4] = (scrn_id & 0xff);
		ba_cmd[5] = (text_id >> 8) & 0xff;
		ba_cmd[6] = (text_id & 0xff);
		ba_cmd.insert(7, m_gb_codec->fromUnicode(s));
	
		transfer_data(ba_cmd);
	}
}

void UIController_Worker::set_visible(uint32_t widget_id, bool visible)
{
	const uint8_t cmd[] = { 0xee, 0xb1, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xfc, 0xff, 0xff };
	QByteArray ba_cmd((char *) cmd, sizeof(cmd));
	uint32_t scrn_id, text_id;
	scrn_id = widget_id >> 16;
	text_id = widget_id & 0x0000ffff;
	ba_cmd[3] = (scrn_id >> 8) & 0xff;
	ba_cmd[4] = (scrn_id & 0xff);
	ba_cmd[5] = (text_id >> 8) & 0xff;
	ba_cmd[6] = (text_id & 0xff);
	ba_cmd[7] = visible;

	transfer_data(ba_cmd);
}

void UIController_Worker::receive_data()
{
	if (m_port) {
		m_data += m_port->readAll();
        QByteArray msg_ba = rcsv_analyze(m_data);
		if (msg_ba.length())
            parse_msg(msg_ba.mid(1, msg_ba.length() - 5));
	}
}

void UIController_Worker::transfer_data(QByteArray data)
{
	if (m_port)
		m_port->write(data);
}

QByteArray UIController_Worker::rcsv_analyze(QByteArray &raw)
{
	const uint8_t postfix[] = { 0xff, 0xfc, 0xff, 0xff };
	QByteArray ba_postfix((char *) postfix, sizeof(postfix));
    QByteArray resp;

	while (raw.length() >= 6) {
        if ((uint8_t) raw[0] != 0xee) {
        	qInfo() << "[UI]Dispose data" << QString("%1").arg(raw[0], 2, 16, QLatin1Char('0'));
			raw.remove(0, 1);
			continue;
		}
		int postfix_pos = raw.indexOf(ba_postfix);
		if (postfix_pos == -1)
            return resp;
		// a complete command sequence found!
        resp = raw.mid(0, postfix_pos + sizeof(postfix));
		raw.remove(0, postfix_pos + sizeof(postfix));
		break;
	}

    return resp;
}

void UIController_Worker::parse_msg(QByteArray msg)
{
	uint32_t scrn_id, ctrl_id, val;

	if ((uint8_t) msg[0] != 0xb1) {
		qInfo() << "[UI]Message not processed";
		return;
	}
	switch ((uint8_t) msg[1]) {
	case 0x01:								// screen's state
		scrn_id = ((uint32_t) msg[2] >> 8) + (uint8_t) msg[3];
		qInfo().noquote() << "[UI]Screen" << QString("%1").arg(scrn_id, 3, 10, QLatin1Char(' '));
		emit scrn_switched(scrn_id);
		break;
	case 0x11:
		switch ((uint8_t) msg[6]) {			// widget's state
		case 0x10:							// button state
			scrn_id = ((uint32_t) msg[2] >> 8) + (uint8_t) msg[3];
			ctrl_id = ((uint32_t) msg[4] >> 8) + (uint8_t) msg[5];
			qInfo().noquote() << "[UI]Button" << QString("(%1, %2) is ").arg(scrn_id, 3, 10, QLatin1Char(' ')).arg(ctrl_id, 3, 10, QLatin1Char(' ')) << (msg[8] ? "Pressed" : "Released");
			if (!(uint8_t) msg[8])
				emit btn_clicked((scrn_id << 16) | ctrl_id);
			break;
		case 0x11:							// text state
			scrn_id = ((uint32_t) msg[2] >> 8) + (uint8_t) msg[3];
			ctrl_id = ((uint32_t) msg[4] >> 8) + (uint8_t) msg[5];
			qInfo().noquote() << "[UI]TextField" << QString("(%1, %2) is changed to \"%3\"").arg(scrn_id, 3, 10, QLatin1Char(' ')).arg(ctrl_id, 3, 10, QLatin1Char(' ')).arg(m_gb_codec->toUnicode(msg.mid(7).constData()));
			emit txt_changed((scrn_id << 16) | ctrl_id, m_gb_codec->toUnicode(msg.mid(7).constData()));
			break;
		case 0x1b:
			scrn_id = ((uint32_t) msg[2] >> 8) + (uint8_t) msg[3];
			ctrl_id = ((uint32_t) msg[4] >> 8) + (uint8_t) msg[5];
			val = (uint32_t) msg[7];
            qInfo().noquote() << "[UI]Select" << QString("(%1, %2) is changed to %3").arg(scrn_id, 3, 10, QLatin1Char(' ')).arg(ctrl_id, 3, 10, QLatin1Char(' ')).arg(val);
			emit sel_changed((scrn_id << 16) | ctrl_id, val);
		}
		break;
	case 0x5a:
		switch ((uint8_t) msg[6]) {			// widget's state
		case 0x1d:							// button state
			scrn_id = ((uint32_t) msg[2] >> 8) + (uint8_t) msg[3];
			ctrl_id = ((uint32_t) msg[4] >> 8) + (uint8_t) msg[5];
			val = ((uint32_t) msg[7] >> 8) + (uint8_t) msg[8];
			qInfo().noquote() << "[UI]Table" << QString("(%1, %2) selects line %3").arg(scrn_id, 3, 10, QLatin1Char(' ')).arg(ctrl_id, 3, 10, QLatin1Char(' ')).arg(val);
			emit tab_sel_changed((scrn_id << 16) | ctrl_id, val);
			break;
		}
	}
}

UIController::UIController(QObject *parent)
{
    QSettings settings(JSONSettings::GetSettingFilePath(), JSONSettings::GetFormat(), QCoreApplication::instance());
    UIController_Worker::SerialConfig param;

    param.port = settings.value(JSON_DISPLAY_DEV).toString();
    param.baud = settings.value(JSON_DISPLAY_BAUD).toUInt();
    param.data_bits = (QSerialPort::DataBits) settings.value(JSON_DISPLAY_DATABITS).toUInt();
    param.stop_bits = (QSerialPort::StopBits) settings.value(JSON_DISPLAY_STOPBITS).toUInt();
    param.parity = (QSerialPort::Parity) settings.value(JSON_DISPLAY_PARITY).toUInt();

	m_coordinator = qobject_cast<Coordinator *>(parent);

	m_worker = new UIController_Worker();
	m_worker->setSerialParam(param);
	m_worker->moveToThread(&m_worker_thread);

	connect(&m_worker_thread, &QThread::finished, m_worker, &QObject::deleteLater);
    connect(this, &UIController::start, m_worker, &UIController_Worker::start_work);
    connect(this, &UIController::stop, m_worker, &UIController_Worker::stop_work);

    m_worker_thread.setObjectName("Display Thread");
	m_worker_thread.start();
}

UIController::~UIController()
{
	m_worker_thread.quit();
	m_worker_thread.wait();
}
